//    Copyright 2013 Serge Kosachiov.
//    This file is part of bulkmail-jsf.
//
//    BulkMail is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    BulkMail is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with BulkMail.  If not, see <http://www.gnu.org/licenses/>.

package bulkmail;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.annotation.PreDestroy;
import javax.faces.context.FacesContext;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.primefaces.event.FileUploadEvent;
import org.primefaces.model.UploadedFile;

public class MailBean {

	private String messageBody = "";
	private String messageSubject = "";
	private String messageAddress = "";
	private List<String> messageAddressList = new ArrayList<String>();

	private String action = "multi"; // default action

	private String attMaxSize = "128";

	private String archType;
	private String volumeSize;

	private List<File> tmpFileList = new ArrayList<File>();
	private List<String> uploadedFileNameList = new ArrayList<String>();
	private String uploadedFileNames = "";
	private boolean alreadyTempDirCreated = false;
	private Path tempUserDir;
	private String tempUserDirName;
	private File tempFile;
	private String tempFileAbsolutePath;
	private String tempFileName;
	private String tempDirName;

	private int numberOfPartsSent = 0;
	private boolean attachmentProblem = false;
	private boolean emailProblem = false;
	private int progressBar = 0;
	private boolean actionStarted = false;

	private boolean sendingReport = true;

	protected Logger logger = Logger.getLogger(this.getClass().getName());

	private LoginBean loginBean = (LoginBean) FacesContext.getCurrentInstance()
			.getExternalContext().getSessionMap().get("loginBean");

	public MailBean() {
		try {
			Properties prop = new Properties();
			prop.load(Thread.currentThread().getContextClassLoader()
					.getResourceAsStream("config.properties"));
			attMaxSize = prop.getProperty("ATT_MAX_SIZE").trim();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public String getAttMaxSize() {
		return attMaxSize;
	}

	public void setAttMaxSize(String attMaxSize) {
		this.attMaxSize = attMaxSize;
	}

	public boolean isActionStarted() {
		return actionStarted;
	}

	public void setActionStarted(boolean actionStarted) {
		this.actionStarted = actionStarted;
	}

	public int getProgressBar() {
		return progressBar;
	}

	public void setProgressBar(int progressBar) {
		this.progressBar = progressBar;
	}

	//
	public boolean getEmailProblem() {
		return emailProblem;
	}

	public boolean getAttachmentProblem() {
		return attachmentProblem;
	}

	//
	public int getNumberOfPartsSent() {
		return numberOfPartsSent;
	}

	public void setNumberOfPartsSent(int numberOfPartsSent) {
		this.numberOfPartsSent = numberOfPartsSent;
	}

	public void setMessageBody(String messageBody) {
		this.messageBody = messageBody;
	}

	public String getMessageBody() {
		return messageBody;
	}

	// //
	public String getMessageSubject() {
		return messageSubject;
	}

	public void setMessageSubject(String messageSubject) {
		this.messageSubject = messageSubject;
	}

	public String getArchType() {
		return archType;
	}

	public void setArchType(String archType) {
		this.archType = archType;
	}

	public String getAction() {
		return action;
	}

	public void setAction(String action) {
		this.action = action;
	}

	public String getUploadedFileNames() {
		return uploadedFileNames;
	}

	public void setUploadedFileNames(String uploadedFileNames) {
		this.uploadedFileNames = uploadedFileNames;
	}

	public String getVolumeSize() {
		return volumeSize;
	}

	public void setVolumeSize(String volumeSize) {
		this.volumeSize = volumeSize;
	}

	public String getMessageAddress() {
		return messageAddress;
	}

	public void setMessageAddress(String messageAddress) {
		this.messageAddress = messageAddress;
	}

	public boolean isSendingReport() {
		return sendingReport;
	}

	public void setSendingReport(boolean sendingReport) {
		this.sendingReport = sendingReport;
	}

	// //
	public synchronized void handleFileUpload(FileUploadEvent event)
			throws IOException {
		logger.info("starting file upload");
		if (!alreadyTempDirCreated) {
			uploadedFileNames = "";
			tempUserDir = Files.createTempDirectory(loginBean.getLogin());
			tempUserDirName = tempUserDir.toString();
			tempFile = File.createTempFile(loginBean.getLogin(), "");
			tempFileAbsolutePath = tempFile.getAbsolutePath();
			tempFileName = tempFile.getName();
			tempDirName = tempFile.getParent();
			tempFile.delete();
			logger.info("temporary directory: " + tempUserDirName + ", file:"
					+ tempFileName);
			alreadyTempDirCreated = true;
		}
		try {
			UploadedFile uploadedFile = event.getFile();
			InputStream stream = uploadedFile.getInputstream();
			long size = uploadedFile.getSize();

			uploadedFileNameList.add(uploadedFile.getFileName());
			byte[] buffer = new byte[(int) size];
			stream.read(buffer, 0, (int) size);
			stream.close();
			logger.info("uploaded file: " + uploadedFile.getFileName()
					+ ", size: " + size + ", number of files: "
					+ uploadedFileNameList.size());
			// //

			File tmpFile = new File(tempUserDirName + (String) File.separator
					+ uploadedFile.getFileName());
			InputStream input = uploadedFile.getInputstream();
			OutputStream output = new FileOutputStream(tmpFile);
			tmpFileList.add(tmpFile);
			tmpFile.deleteOnExit();

			uploadedFileNames = "";
			for (String s : uploadedFileNameList) {
				uploadedFileNames += s + "\n";
			}

			try {
				IOUtils.copy(input, output);
			} finally {
				IOUtils.closeQuietly(output);
				IOUtils.closeQuietly(input);
			}

		} catch (Exception ioe) {
			logger.info("uploaded file: unsuccessful");
		}
	}

	public void removeAttachment() {
		try {
			uploadedFileNames = "";
			uploadedFileNameList.clear();
			if (alreadyTempDirCreated) FileUtils.deleteDirectory(new File(tempUserDirName));
			alreadyTempDirCreated = false;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public String submit() {
		if (action.equals("multi"))
			sendMulti();
		if (action.equals("togroup"))
			sendToGroup();
		return "/user/status";
	}
	
	@PreDestroy
	public void cleanup() throws Exception {
		logger.info("cleanup");
		removeAttachment();
	}
	// //

	private String sendMulti() {

		attachmentProblem = false;
		if (uploadedFileNameList.isEmpty() || tmpFileList.isEmpty()) {
			attachmentProblem = true;
			return action;
		}

		messageAddressList = Arrays.asList(messageAddress.split("\\s*,\\s*"));
		for (String a : messageAddressList) {
			try {
				emailProblem = false;
				InternetAddress probeEmailAddr = new InternetAddress(a);
				probeEmailAddr.validate();
			} catch (AddressException ex) {
				emailProblem = true;
				return action;
			}
		}

		actionStarted = true;

		Properties prop = new Properties();
		try {
			prop.load(Thread.currentThread().getContextClassLoader()
					.getResourceAsStream("config.properties"));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		Properties sysprop = System.getProperties();
		sysprop.put("mail.smtp.host", prop.getProperty("SMTP_SERVER"));

		Session session = Session.getInstance(sysprop, null);

		final Runtime rt = Runtime.getRuntime();
		try {
			String execString = "";
			if (archType.equals("7z"))
				execString = "7z a -v" + volumeSize + " "
						+ tempFileAbsolutePath + ".7z" + " " + tempUserDirName;
			if (archType.equals("rar"))
				execString = "rar a -r -ep1 -v" + volumeSize + " "
						+ tempFileAbsolutePath + ".rar" + " " + tempUserDirName;

			logger.info("execute: " + execString);
			Process pr = rt.exec(execString);
			pr.waitFor();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		FilenameFilter fileFilter = new FilenameFilter() {
			public boolean accept(File directory, String name) {
				if (archType.equals("7z"))
					return name.startsWith(tempFileName + ".7z");
				if (archType.equals("rar"))
					return name.matches(tempFileName + ".*rar");
				return false;
			}
		};

		File directory = new File(tempDirName);
		directory.deleteOnExit();

		File[] archiveParts = directory.listFiles(fileFilter);
		Arrays.sort(archiveParts);
		int numberOfParts = archiveParts.length;
		int currentPart = 1;

		for (File archivePart : archiveParts) {
			archivePart.deleteOnExit();
			try {
				// create a message
				MimeMessage msg = new MimeMessage(session);
				msg.setFrom(new InternetAddress(loginBean.getLogin() + "@"
						+ prop.getProperty("SMTP_DOMAIN")));
				InternetAddress[] address = InternetAddress
						.parse(messageAddress);
				msg.setRecipients(Message.RecipientType.TO, address);
				msg.setSubject(messageSubject + " "
						+ Integer.toString(currentPart) + "/"
						+ Integer.toString(numberOfParts));

				// create and fill the first message part
				MimeBodyPart mbp1 = new MimeBodyPart();
				mbp1.setText(messageBody);

				// create the second message part
				MimeBodyPart mbp2 = new MimeBodyPart();

				// attach the file to the message
				// FileDataSource fds = new
				// FileDataSource(file.getAbsolutePath());
				FileDataSource fds = new FileDataSource(archivePart);
				mbp2.setDataHandler(new DataHandler(fds));
				try {
					// mbp2.setFileName(MimeUtility.encodeText(uploadedFileName));
					String partName = archivePart.getName();
					mbp2.setFileName(MimeUtility.encodeText(partName));
				} catch (UnsupportedEncodingException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

				// create the Multipart and add its parts to it
				Multipart mp = new MimeMultipart();
				mp.addBodyPart(mbp1);
				mp.addBodyPart(mbp2);

				// add the Multipart to the message
				msg.setContent(mp);

				// set the Date: header
				msg.setSentDate(new Date());

				// send the message
				logger.info("send to: " + messageAddress + ", fragment: "
						+ currentPart);
				progressBar = (int) (100 * (double) currentPart / numberOfParts);
				Transport.send(msg);

				archivePart.delete();
				currentPart++;

			} catch (MessagingException mex) {
				mex.printStackTrace();
				Exception ex = null;
				if ((ex = mex.getNextException()) != null) {
					ex.printStackTrace();
				}
			}

		}
		numberOfPartsSent = --currentPart;

		if (sendingReport) {
			String sendingReportBody = messageAddress + "\n\n" + uploadedFileNames;
			try {
				MimeMessage msg = new MimeMessage(session);
				msg.setFrom(new InternetAddress(prop.getProperty("ADMIN_EMAIL")));
				msg.setRecipient(Message.RecipientType.TO,
						new InternetAddress(loginBean.getLogin() + "@" + prop.getProperty("SMTP_DOMAIN")));
				msg.setSubject(prop.getProperty("REPORT_SUBJECT"));
				MimeBodyPart mbp1 = new MimeBodyPart();
				mbp1.setText(sendingReportBody);
				Multipart mp = new MimeMultipart();
				mp.addBodyPart(mbp1);
				msg.setContent(mp);
				msg.setSentDate(new Date());

				Transport.send(msg);
				logger.info("sent sending report to: " + loginBean.getLogin() + "@" + prop.getProperty("SMTP_DOMAIN"));
			}

			catch (MessagingException mex) {
				mex.printStackTrace();
				Exception ex = null;
				if ((ex = mex.getNextException()) != null) {
					ex.printStackTrace();
				}
			}
		}

		actionStarted = false;
		return action;
	}

	private String sendToGroup() {

		actionStarted = true;

		LdapBean ldapBean = (LdapBean) FacesContext.getCurrentInstance()
				.getExternalContext().getSessionMap().get("ldapBean");

		Properties prop = new Properties();
		try {
			prop.load(Thread.currentThread().getContextClassLoader()
					.getResourceAsStream("config.properties"));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		Properties sysprop = System.getProperties();
		sysprop.put("mail.smtp.host", prop.getProperty("SMTP_SERVER"));

		Session session = Session.getInstance(sysprop, null);
		int numberOfParts = ldapBean.getSelectedGroups().size();
		int currentPart = 1;

		for (String selectedGroup : ldapBean.getSelectedGroups()) {
			String mail = ldapBean.mapUsers.get(selectedGroup);
			logger.info("selected gid: " + selectedGroup + ", mail to: " + mail);

			try {
				MimeMessage msg = new MimeMessage(session);
				msg.setFrom(new InternetAddress(loginBean.getLogin() + "@"
						+ prop.getProperty("SMTP_DOMAIN")));
				//
				msg.addRecipients(Message.RecipientType.CC,
						InternetAddress.parse(mail));
				msg.setSubject(messageSubject);

				// create and fill the first message part
				MimeBodyPart mbp1 = new MimeBodyPart();
				mbp1.setText(messageBody);

				// create the Multipart and add its parts to it
				Multipart mp = new MimeMultipart();
				mp.addBodyPart(mbp1);

				// add the Multipart to the message
				msg.setContent(mp);

				// set the Date: header
				msg.setSentDate(new Date());

				// send the message
				logger.info("send to group: " + selectedGroup);
				Transport.send(msg);

				numberOfPartsSent++;

				currentPart++;
				progressBar = (int) (100 * (double) currentPart / numberOfParts);

			} catch (MessagingException mex) {
				mex.printStackTrace();
				Exception ex = null;
				if ((ex = mex.getNextException()) != null) {
					ex.printStackTrace();
				}
			}

		}

		actionStarted = false;
		return action;

	}

}
